﻿using System;
using System.ComponentModel;
using NUnit.Framework;
using Rhino.Mocks;
using WPFCABShell.Shell.Interface.Services.Options;
using WPFCABShell.Shell.Interface.Services.Settings;
using WPFCABShell.Shell.Options;
using WPFCABShell.UnitTest.Shared;

namespace WPFCABShell.UnitTest.Shell.Options
{
	[TestFixture]
	public sealed class OptionsPresenterTest : Shared.UnitTest
	{
		private TestableWorkItem _workItem;
		private ISettingsService _settingsService;
		private OptionsPresenter _optionsPresenter;
		private PropertyChangedEventArgs _propertyChangedEventArgs;

		protected override void SetUpCore()
		{
			base.SetUpCore();
			ClearEvents();
			_workItem = new TestableWorkItem();
			_settingsService = Mocks.DynamicMock<ISettingsService>();
			_optionsPresenter = new OptionsPresenter(_workItem, _settingsService);

			_optionsPresenter.PropertyChanged += delegate(object sender, PropertyChangedEventArgs e)
			{
				_propertyChangedEventArgs = e;
			};
		}

		[Test]
		[ExpectedException(typeof(InvalidOperationException), ExpectedMessage = "Options pane type must be non-null.")]
		public void SelectOptions_ShouldThrowIfNoPaneType()
		{
			IOptions options = Mocks.DynamicMock<IOptions>();
			Expect.Call(options.PaneType).Repeat.Any().Return(null);
			Mocks.ReplayAll();

			_optionsPresenter.SelectOptions(options);
		}

		[Test]
		[ExpectedException(typeof(InvalidOperationException), ExpectedMessage = "Options pane type must implement IOptionsPane interface.")]
		public void SelectOptions_ShouldThrowIfPaneTypeDoesntImplementRequiredInterface()
		{
			IOptions options = Mocks.DynamicMock<IOptions>();
			Expect.Call(options.PaneType).Repeat.Any().Return(typeof(string));
			Mocks.ReplayAll();

			_optionsPresenter.SelectOptions(options);
		}

		[Test]
		[ExpectedException(typeof(InvalidOperationException), ExpectedMessage = "Options pane type must have a default constructor.")]
		public void SelectOptions_ShouldThrowIfPaneTypeDoesntHaveDefaultConstructor()
		{
			IOptions options = Mocks.DynamicMock<IOptions>();
			IOptionsPane optionsPane = Mocks.DynamicMock<IOptionsPane>();
			Expect.Call(options.PaneType).Repeat.Any().Return(optionsPane.GetType());
			Mocks.ReplayAll();

			Assert.IsNull(_optionsPresenter.CurrentPane);
			_optionsPresenter.SelectOptions(options);
			Assert.AreSame(optionsPane, _optionsPresenter.CurrentPane);
		}

		[Test]
		public void SelectOptions_ShouldAssignPaneForOptions()
		{
			IOptions options = Mocks.DynamicMock<IOptions>();
			Expect.Call(options.PaneType).Repeat.Any().Return(typeof(MockOptionsPane));
			ISettings settings = Mocks.DynamicMock<ISettings>();
			Expect.Call(_settingsService.GetSettings(null)).IgnoreArguments().Repeat.Any().Return(settings);
			Mocks.ReplayAll();

			Assert.IsNull(_optionsPresenter.CurrentPane);
			Assert.IsNull(_propertyChangedEventArgs);
			_optionsPresenter.SelectOptions(options);
			Assert.IsNotNull(_optionsPresenter.CurrentPane);
			Assert.IsNotNull(_propertyChangedEventArgs);
			Assert.AreEqual("CurrentPane", _propertyChangedEventArgs.PropertyName);
			Assert.AreEqual(typeof(MockOptionsPane), _optionsPresenter.CurrentPane.GetType());
			IOptionsPane optionsPane = _optionsPresenter.CurrentPane;

			//selecting null should result in a null current pane
			_optionsPresenter.SelectOptions(null);
			Assert.IsNull(_optionsPresenter.CurrentPane);

			//re-selecting previous options should re-use same options pane instance
			_optionsPresenter.SelectOptions(options);
			Assert.IsNotNull(_optionsPresenter.CurrentPane);
			Assert.AreSame(optionsPane, _optionsPresenter.CurrentPane);
		}

		[Test]
		public void ApplyChanges_ShouldDoNothingIfNoOptions()
		{
			_optionsPresenter.ApplyChanges();
		}

		[Test]
		public void ApplyChanges_ShouldAssignPaneDataToSettings()
		{
			IOptions options = Mocks.DynamicMock<IOptions>();
			Expect.Call(options.PaneType).Repeat.Any().Return(typeof(MockOptionsPane));
			ISettings settings = Mocks.DynamicMock<ISettings>();
			settings["Data"] = "my data";
			LastCall.Repeat.Once();
			Expect.Call(_settingsService.GetSettings(null)).IgnoreArguments().Repeat.Any().Return(settings);
			Mocks.ReplayAll();

 			_optionsPresenter.SelectOptions(options);
			_optionsPresenter.CurrentPane.Data = "my data";
			_optionsPresenter.ApplyChanges();
			Assert.IsTrue(((MockOptionsPane)_optionsPresenter.CurrentPane).Applied.Value);
		}

		[Test]
		public void RevertChanges_ShouldDoNothingIfNoOptions()
		{
			_optionsPresenter.RevertChanges();
		}

		[Test]
		public void RevertChanges_ShouldClosePanesWithoutApplyingChanges()
		{
			IOptions options = Mocks.DynamicMock<IOptions>();
			Expect.Call(options.PaneType).Repeat.Any().Return(typeof(MockOptionsPane));
			ISettings settings = Mocks.DynamicMock<ISettings>();
			settings["Data"] = "my data";
			LastCall.Repeat.Never();
			Expect.Call(_settingsService.GetSettings(null)).IgnoreArguments().Repeat.Any().Return(settings);
			Mocks.ReplayAll();

			_optionsPresenter.SelectOptions(options);
			_optionsPresenter.CurrentPane.Data = "my data";
			_optionsPresenter.RevertChanges();
			Assert.IsFalse(((MockOptionsPane)_optionsPresenter.CurrentPane).Applied.Value);
		}

		#region Supporting Members

		private void ClearEvents()
		{
			_propertyChangedEventArgs = null;
		}

		#endregion

		#region Supporting Types

		private sealed class MockOptionsPane : IOptionsPane
		{
			private string _data;
			private bool? _applied;

			public object Data
			{
				get
				{
					return _data;
				}
				set
				{
					_data = value as string;
				}
			}

			public bool? Applied
			{
				get
				{
					return _applied;
				}
			}

			public object CreateData()
			{
				return string.Empty;
			}

			public void Closed(bool applied)
			{
				_applied = applied;
			}
		}

		#endregion
	}
}
